Tones {
	classvar <>all;
	classvar <>midiChan;
	classvar <>noteArray;
	classvar <>noteVals;
	var routine;

	*initClass { | midi |
		midiChan = midi;
		noteArray = ( 26 .. 60 ); 
		noteVals = noteArray.asSet;
		all = [];
	}

	*deriveScale { | steps |
		var total = 0;
		^steps.collect{ | step |
			total = total + step;
			total % 12
		}.asSet.asArray.sort;
	}

	*lyseScale { | scale |
		var last = scale[ 0 ];
		var result;
		^scale.[1..].collect{ | degree |
			result = (degree - last) % 12;
			last = degree;
			result
		}
	}

	*expandScale { | scale |
		var missing = ( 0..11 ).select{ | x | scale.find ( [ x ] ).isNil };
		^( scale ++ missing.choose ).sort;
	}

	*shrinkScale { | scale |
		^scale.scramble.[ 1.. ].sort;
	}

	*normalizeScale { | scale |
		scale = scale.asSet.asArray.sort;
		^scale - scale[0];
	}

	duration {
		^( 0.2 + 0.02.rand);
	}
	
	velocity {
		^( 27.rand + 100 );
	}

	play {
		if( not( routine.isPlaying ),
			{
			  routine.reset;
			  routine.play;
			}
		);
	}

	stop {
		routine.stop;
	}

	initRoutine {
		routine = Routine{
			loop {
				midiChan.noteOn( 0, this.note(), this.velocity() );
				this.duration().wait } };
		"new note player".postln;
		all = all ++ this;
	}

	isPlaying {
		^routine.isPlaying;
	}
}

WhiteTone : Tones {
	*new {
		^super.new.init;
	}

	init {
		super.initRoutine();
	}

	note {
		^noteVals.choose;
	}
	duration {
		^( 0.02 + 0.2.rand );
	}
}

SerialTone : Tones {
	var used;

	*new {
		^super.new.init;
	}

	init {
		used = Set[];
		super.initRoutine();
	}

	note {
		var diff = used.symmetricDifference( noteVals );
		var newNote;
		if( diff.size > 0, {
			newNote = diff.choose;
			used.add( newNote );
		}, {
			newNote = noteVals.choose;
			used = Set[ newNote ] } );
		^newNote;
	}
}

Melody : Tones {
	var <>melody;
	var idx;
	var <>scale;
	var <>rhythm;

	*new {
		^super.new.init;
	}
	
	init {
		melody = [ Tones.noteVals.choose ];
		idx = 0;
		scale = [ 0, 2, 4, 5, 7, 9, 11 ]; // major scale
		rhythm = [ 0.25, 0.1, 0.15 ];
		super.initRoutine();
	}

	expand {
		var newTone = scale.choose + 20 + (4.rand * 12);
		if ( newTone < 26,
			{ newTone = newTone + 12 } );
		if ( newTone > 60,
			{ newTone = newTone - 12 } );
		melody = melody ++ [ newTone ];
	}

	prune {
		melody = melody[ 1.. ];
	}

	note {
		^if( melody.size > 0, {
			idx = idx + 1 % melody.size;
			melody[ idx ];
		}, {
			noteVals.asArray[ 0 ] } );
	}

	duration {
		^( rhythm @@ idx );
	}
}

